"""
Copyright (c) 2016-2018, Jairus Martin.

Distributed under the terms of the GPL v3 License.

The full license is in the file LICENSE, distributed with this software.

Created on Sep 30, 2016

@author: jrm
"""
import os
from atom.api import Instance, Typed, Unicode, observe, set_default

from OCC.Bnd import Bnd_Box
from OCC.BRep import BRep_Builder
from OCC.BRepBndLib import brepbndlib_Add
from OCC.BRepBuilderAPI import (
    BRepBuilderAPI_MakeShape, BRepBuilderAPI_MakeFace, BRepBuilderAPI_Transform
)
from OCC.BRepPrimAPI import (
    BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCone,
    BRepPrimAPI_MakeCylinder, BRepPrimAPI_MakeHalfSpace, BRepPrimAPI_MakePrism,
    BRepPrimAPI_MakeSphere, BRepPrimAPI_MakeWedge, BRepPrimAPI_MakeTorus,
    BRepPrimAPI_MakeRevol,
)
from OCC.BRepTools import BRepTools_WireExplorer, breptools_Read


from OCC.gp import gp_Pnt, gp_Dir, gp_Vec, gp_Ax1, gp_Ax2, gp_Ax3, gp_Trsf

from OCC.TopAbs import (
    TopAbs_VERTEX, TopAbs_EDGE, TopAbs_FACE, TopAbs_WIRE,
    TopAbs_SHELL, TopAbs_SOLID, TopAbs_COMPOUND,
    TopAbs_COMPSOLID
)
from OCC.TopExp import TopExp_Explorer, topexp_MapShapesAndAncestors
from OCC.TopoDS import (
    topods, TopoDS_Wire, TopoDS_Vertex, TopoDS_Edge,
    TopoDS_Face, TopoDS_Shell, TopoDS_Solid,
    TopoDS_Compound, TopoDS_CompSolid, topods_Edge,
    topods_Vertex, TopoDS_Shape
)
from OCC.TopTools import (
    TopTools_ListOfShape,
    TopTools_ListIteratorOfListOfShape,
    TopTools_IndexedDataMapOfShapeListOfShape
)

from OCC.IGESControl import IGESControl_Reader
from OCC.IFSelect import IFSelect_RetDone, IFSelect_ItemsByEntity
from OCC.STEPCAFControl import STEPCAFControl_Reader
from OCC.STEPControl import STEPControl_Reader
from OCC.StlAPI import StlAPI_Reader

from ..shape import (
    ProxyShape, ProxyFace, ProxyBox, ProxyCone, ProxyCylinder,
    ProxyHalfSpace, ProxyPrism, ProxySphere, ProxyWedge,
    ProxyTorus, ProxyRevol, ProxyRawShape, ProxyLoadShape, BBox
)


def coerce_axis(value):
    return gp_Ax2(gp_Pnt(*value[0]), gp_Dir(*value[1]))


class WireExplorer(object):
    """ Wire traversal """
    def __init__(self, wire):
        assert isinstance(wire, TopoDS_Wire), 'not a TopoDS_Wire'
        self.wire = wire
        self.wire_explorer = BRepTools_WireExplorer(self.wire)
        self.done = False

    def _reinitialize(self):
        self.wire_explorer = BRepTools_WireExplorer(self.wire)
        self.done = False

    def _loop_topo(self, edges=True):
        if self.done:
            self._reinitialize()
        topologyType = topods_Edge if edges else topods_Vertex
        seq = []
        hashes = []  # list that stores hashes to avoid redundancy
        occ_seq = TopTools_ListOfShape()
        while self.wire_explorer.More():
            # loop edges
            if edges:
                current_item = self.wire_explorer.Current()
            # loop vertices
            else:
                current_item = self.wire_explorer.CurrentVertex()
            current_item_hash = current_item.__hash__()
            if not current_item_hash in hashes:
                hashes.append(current_item_hash)
                occ_seq.Append(current_item)
            self.wire_explorer.Next()

        # Convert occ_seq to python list
        occ_iterator = TopTools_ListIteratorOfListOfShape(occ_seq)
        while occ_iterator.More():
            topo_to_add = topologyType(occ_iterator.Value())
            seq.append(topo_to_add)
            occ_iterator.Next()
        self.done = True
        return iter(seq)

    def ordered_edges(self):
        return self._loop_topo(edges=True)

    def ordered_vertices(self):
        return self._loop_topo(edges=False)


class Topology(object):
    """ Topology traversal
    from the pythonocc examples.
    """

    def __init__(self, myShape, ignore_orientation=False):
        """ implements topology traversal from any TopoDS_Shape
        this class lets you find how various topological entities are connected 
        from one to another
        find the faces connected to an edge, find the vertices this edge is 
        made from, get all faces connected to
        a vertex, and find out how many topological elements are connected 
        from a source

        *note* when traversing TopoDS_Wire entities, its advised to use the 
        specialized ``WireExplorer`` class, which will return the vertices / 
        edges in the expected order

        :param myShape: the shape which topology will be traversed

        :param ignore_orientation: filter out TopoDS_* entities of similar 
        TShape but different Orientation

        for instance, a cube has 24 edges, 4 edges for each of 6 faces

        that results in 48 vertices, while there are only 8 vertices that have
        a unique geometric coordinate

        in certain cases ( computing a graph from the topology ) its preferable 
        to return topological entities that share similar geometry, though 
        differ in orientation by setting the ``ignore_orientation`` variable
        to True, in case of a cube, just 12 edges and only 8 vertices will be 
        returned

        for further reference see TopoDS_Shape IsEqual / IsSame methods

        """
        self.myShape = myShape
        self.ignore_orientation = ignore_orientation

        # the topoFactory dicts maps topology types and functions that can
        # create this topology
        self.topoFactory = {
            TopAbs_VERTEX: topods.Vertex,
            TopAbs_EDGE: topods.Edge,
            TopAbs_FACE: topods.Face,
            TopAbs_WIRE: topods.Wire,
            TopAbs_SHELL: topods.Shell,
            TopAbs_SOLID: topods.Solid,
            TopAbs_COMPOUND: topods.Compound,
            TopAbs_COMPSOLID: topods.CompSolid
        }

    def _loop_topo(self, topologyType, topologicalEntity=None,
                   topologyTypeToAvoid=None):
        '''
        this could be a faces generator for a python TopoShape class
        that way you can just do:
        for face in srf.faces:
            processFace(face)
        '''
        topoTypes = {TopAbs_VERTEX: TopoDS_Vertex,
                     TopAbs_EDGE: TopoDS_Edge,
                     TopAbs_FACE: TopoDS_Face,
                     TopAbs_WIRE: TopoDS_Wire,
                     TopAbs_SHELL: TopoDS_Shell,
                     TopAbs_SOLID: TopoDS_Solid,
                     TopAbs_COMPOUND: TopoDS_Compound,
                     TopAbs_COMPSOLID: TopoDS_CompSolid}

        assert topologyType in topoTypes.keys(), '%s not one of %s' % (
            topologyType, topoTypes.keys())
        self.topExp = TopExp_Explorer()
        # use self.myShape if nothing is specified
        if topologicalEntity is None and topologyTypeToAvoid is None:
            self.topExp.Init(self.myShape, topologyType)
        elif topologicalEntity is None and topologyTypeToAvoid is not None:
            self.topExp.Init(self.myShape, topologyType, topologyTypeToAvoid)
        elif topologyTypeToAvoid is None:
            self.topExp.Init(topologicalEntity, topologyType)
        elif topologyTypeToAvoid:
            self.topExp.Init(topologicalEntity,
                             topologyType,
                             topologyTypeToAvoid)
        seq = []
        hashes = []  # list that stores hashes to avoid redundancy
        occ_seq = TopTools_ListOfShape()
        while self.topExp.More():
            current_item = self.topExp.Current()
            current_item_hash = current_item.__hash__()

            if not current_item_hash in hashes:
                hashes.append(current_item_hash)
                occ_seq.Append(current_item)

            self.topExp.Next()
        # Convert occ_seq to python list
        occ_iterator = TopTools_ListIteratorOfListOfShape(occ_seq)
        while occ_iterator.More():
            topo_to_add = self.topoFactory[topologyType](occ_iterator.Value())
            seq.append(topo_to_add)
            occ_iterator.Next()

        if self.ignore_orientation:
            # filter out those entities that share the same TShape
            # but do *not* share the same orientation
            filter_orientation_seq = []
            for i in seq:
                _present = False
                for j in filter_orientation_seq:
                    if i.IsSame(j):
                        _present = True
                        break
                if _present is False:
                    filter_orientation_seq.append(i)
            return filter_orientation_seq
        else:
            return iter(seq)

    def faces(self):
        """ loops over all faces """
        return self._loop_topo(TopAbs_FACE)

    def _number_of_topo(self, iterable):
        n = 0
        for i in iterable:
            n += 1
        return n

    def number_of_faces(self):
        return self._number_of_topo(self.faces())

    def vertices(self):
        '''
        loops over all vertices
        '''
        return self._loop_topo(TopAbs_VERTEX)

    def number_of_vertices(self):
        return self._number_of_topo(self.vertices())

    def edges(self):
        '''
        loops over all edges
        '''
        return self._loop_topo(TopAbs_EDGE)

    def number_of_edges(self):
        return self._number_of_topo(self.edges())

    def wires(self):
        '''
        loops over all wires
        '''
        return self._loop_topo(TopAbs_WIRE)

    def number_of_wires(self):
        return self._number_of_topo(self.wires())

    def shells(self):
        '''
        loops over all shells
        '''
        return self._loop_topo(TopAbs_SHELL, None)

    def number_of_shells(self):
        return self._number_of_topo(self.shells())

    def solids(self):
        '''
        loops over all solids
        '''
        return self._loop_topo(TopAbs_SOLID, None)

    def number_of_solids(self):
        return self._number_of_topo(self.solids())

    def comp_solids(self):
        '''
        loops over all compound solids
        '''
        return self._loop_topo(TopAbs_COMPSOLID)

    def number_of_comp_solids(self):
        return self._number_of_topo(self.comp_solids())

    def compounds(self):
        '''
        loops over all compounds
        '''
        return self._loop_topo(TopAbs_COMPOUND)

    def number_of_compounds(self):
        return self._number_of_topo(self.compounds())

    def ordered_vertices_from_wire(self, wire):
        '''
        @param wire: TopoDS_Wire
        '''
        we = WireExplorer(wire)
        return we.ordered_vertices()

    def number_of_ordered_vertices_from_wire(self, wire):
        return self._number_of_topo(self.ordered_vertices_from_wire(wire))

    def ordered_edges_from_wire(self, wire):
        '''
        @param wire: TopoDS_Wire
        '''
        we = WireExplorer(wire)
        return we.ordered_edges()

    def number_of_ordered_edges_from_wire(self, wire):
        return self._number_of_topo(self.ordered_edges_from_wire(wire))

    def _map_shapes_and_ancestors(self, topoTypeA, topoTypeB, topologicalEntity):
        '''
        using the same method
        @param topoTypeA:
        @param topoTypeB:
        @param topologicalEntity:
        '''
        topo_set = set()
        _map = TopTools_IndexedDataMapOfShapeListOfShape()
        topexp_MapShapesAndAncestors(self.myShape, topoTypeA, topoTypeB, _map)
        results = _map.FindFromKey(topologicalEntity)
        if results.IsEmpty():
            yield None

        topology_iterator = TopTools_ListIteratorOfListOfShape(results)
        while topology_iterator.More():

            topo_entity = self.topoFactory[topoTypeB](topology_iterator.Value())

            # return the entity if not in set
            # to assure we're not returning entities several times
            if not topo_entity in topo_set:
                if self.ignore_orientation:
                    unique = True
                    for i in topo_set:
                        if i.IsSame(topo_entity):
                            unique = False
                            break
                    if unique:
                        yield topo_entity
                else:
                    yield topo_entity

            topo_set.add(topo_entity)
            topology_iterator.Next()

    def _number_shapes_ancestors(self, topoTypeA, topoTypeB, topologicalEntity):
        '''returns the number of shape ancestors
        If you want to know how many edges a faces has:
        _number_shapes_ancestors(self, TopAbs_EDGE, TopAbs_FACE, edg)
        will return the number of edges a faces has
        @param topoTypeA:
        @param topoTypeB:
        @param topologicalEntity:
        '''
        topo_set = set()
        _map = TopTools_IndexedDataMapOfShapeListOfShape()
        topexp_MapShapesAndAncestors(self.myShape, topoTypeA, topoTypeB, _map)
        results = _map.FindFromKey(topologicalEntity)
        if results.IsEmpty():
            return None
        topology_iterator = TopTools_ListIteratorOfListOfShape(results)
        while topology_iterator.More():
            topo_set.add(topology_iterator.Value())
            topology_iterator.Next()
        return len(topo_set)

    # ======================================================================
    # EDGE <-> FACE
    # ======================================================================
    def faces_from_edge(self, edge):
        """

        :param edge:
        :return:
        """
        return self._map_shapes_and_ancestors(TopAbs_EDGE, TopAbs_FACE, edge)

    def number_of_faces_from_edge(self, edge):
        """

        :param edge:
        :return:
        """
        return self._number_shapes_ancestors(TopAbs_EDGE, TopAbs_FACE, edge)

    def edges_from_face(self, face):
        """

        :param face:
        :return:
        """
        return self._loop_topo(TopAbs_EDGE, face)

    def number_of_edges_from_face(self, face):
        cnt = 0
        for i in self._loop_topo(TopAbs_EDGE, face):
            cnt += 1
        return cnt

    # ======================================================================
    # VERTEX <-> EDGE
    # ======================================================================
    def vertices_from_edge(self, edg):
        return self._loop_topo(TopAbs_VERTEX, edg)

    def number_of_vertices_from_edge(self, edg):
        cnt = 0
        for i in self._loop_topo(TopAbs_VERTEX, edg):
            cnt += 1
        return cnt

    def edges_from_vertex(self, vertex):
        return self._map_shapes_and_ancestors(TopAbs_VERTEX, TopAbs_EDGE, vertex)

    def number_of_edges_from_vertex(self, vertex):
        return self._number_shapes_ancestors(TopAbs_VERTEX, TopAbs_EDGE, vertex)

    # ======================================================================
    # WIRE <-> EDGE
    # ======================================================================
    def edges_from_wire(self, wire):
        return self._loop_topo(TopAbs_EDGE, wire)

    def number_of_edges_from_wire(self, wire):
        cnt = 0
        for i in self._loop_topo(TopAbs_EDGE, wire):
            cnt += 1
        return cnt

    def wires_from_edge(self, edg):
        return self._map_shapes_and_ancestors(TopAbs_EDGE, TopAbs_WIRE, edg)

    def wires_from_vertex(self, edg):
        return self._map_shapes_and_ancestors(TopAbs_VERTEX, TopAbs_WIRE, edg)

    def number_of_wires_from_edge(self, edg):
        return self._number_shapes_ancestors(TopAbs_EDGE, TopAbs_WIRE, edg)

    # ======================================================================
    # WIRE <-> FACE
    # ======================================================================
    def wires_from_face(self, face):
        return self._loop_topo(TopAbs_WIRE, face)

    def number_of_wires_from_face(self, face):
        cnt = 0
        for i in self._loop_topo(TopAbs_WIRE, face):
            cnt += 1
        return cnt

    def faces_from_wire(self, wire):
        return self._map_shapes_and_ancestors(TopAbs_WIRE, TopAbs_FACE, wire)

    def number_of_faces_from_wires(self, wire):
        return self._number_shapes_ancestors(TopAbs_WIRE, TopAbs_FACE, wire)

    # ======================================================================
    # VERTEX <-> FACE
    # ======================================================================
    def faces_from_vertex(self, vertex):
        return self._map_shapes_and_ancestors(TopAbs_VERTEX, TopAbs_FACE, vertex)

    def number_of_faces_from_vertex(self, vertex):
        return self._number_shapes_ancestors(TopAbs_VERTEX, TopAbs_FACE, vertex)

    def vertices_from_face(self, face):
        return self._loop_topo(TopAbs_VERTEX, face)

    def number_of_vertices_from_face(self, face):
        cnt = 0
        for i in self._loop_topo(TopAbs_VERTEX, face):
            cnt += 1
        return cnt

    # ======================================================================
    # FACE <-> SOLID
    # ======================================================================
    def solids_from_face(self, face):
        return self._map_shapes_and_ancestors(TopAbs_FACE, TopAbs_SOLID, face)

    def number_of_solids_from_face(self, face):
        return self._number_shapes_ancestors(TopAbs_FACE, TopAbs_SOLID, face)

    def faces_from_solids(self, solid):
        return self._loop_topo(TopAbs_FACE, solid)

    def number_of_faces_from_solids(self, solid):
        cnt = 0
        for i in self._loop_topo(TopAbs_FACE, solid):
            cnt += 1
        return cnt


class OccShape(ProxyShape):
    #: A reference to the toolkit shape created by the proxy.
    shape = Typed(BRepBuilderAPI_MakeShape)
    
    #: Topology explorer of the shape
    topology = Typed(Topology)

    #: Class reference url
    reference = Unicode()
    
    # -------------------------------------------------------------------------
    # Initialization API
    # -------------------------------------------------------------------------
    def create_shape(self):
        """ Create the toolkit shape for the proxy object.

        This method is called during the top-down pass, just before the
        'init_shape()' method is called. This method should create the
        toolkit widget and assign it to the 'widget' attribute.

        """
        raise NotImplementedError

    def init_shape(self):
        """ Initialize the state of the toolkit widget.

        This method is called during the top-down pass, just after the
        'create_widget()' method is called. This method should init the
        state of the widget. The child widgets will not yet be created.

        """
        pass

    def init_layout(self):
        """ Initialize the layout of the toolkit shape.

        This method is called during the bottom-up pass. This method
        should initialize the layout of the widget. The child widgets
        will be fully initialized and layed out when this is called.

        """
        pass
    
    def activate_top_down(self):
        """ Activate the proxy for the top-down pass.

        """
        self.create_shape()
        self.init_shape()
        
    def activate_bottom_up(self):
        """ Activate the proxy tree for the bottom-up pass.

        """
        self.init_layout()
    
    def _default_topology(self):
        try:
            return Topology(self.shape.Shape())
        except:
            return None
    
    @observe('shape')
    def update_topology(self, change):
        if self.shape:
            self.topology = self._default_topology()

    @observe('shape')    
    def update_display(self, change):
        parent = self.parent()
        if parent:
            parent.update_display(change)
        
    def set_direction(self, direction):
        self.create_shape()
        
    def set_axis(self, axis):
        self.create_shape()

    def parent_shape(self):
        return self.parent().shape
    
    def get_bounding_box(self, shape=None):
        shape = shape or self.shape
        if not shape:
            return BBox()
        bbox = Bnd_Box()
        if hasattr(shape, 'Shape'):
            brepbndlib_Add(shape.Shape(), bbox)
        else:
            brepbndlib_Add(shape, bbox)
        return BBox(*bbox.Get())


class OccDependentShape(OccShape):
    """ Shape that is dependent on another shape """
    
    def create_shape(self):
        """ Create the toolkit shape for the proxy object.
        
        Operations depend on child or properties so they cannot be created
        in the top down pass but rather must be done in the init_layout method.
        
        """
        pass

    def init_layout(self):
        """ Initialize the layout of the toolkit shape.

        This method is called during the bottom-up pass. This method
        should initialize the layout of the widget. The child widgets
        will be fully initialized and layed out when this is called.

        """
        for child in self.children():
            self.child_added(child)
        self.update_shape({})
    
    def update_shape(self, change):
        """ Must be implmented in subclasses to create the shape
            when the dependent shapes change.
        """
        raise NotImplementedError
        
    def child_added(self, child):
        super(OccDependentShape, self).child_added(child)
        if isinstance(child, OccShape):
            child.observe('shape', self.update_shape)
        
    def child_removed(self, child):
        super(OccDependentShape, self).child_removed(child)
        if isinstance(child, OccShape):
            child.unobserve('shape', self.update_shape)
            
    def set_direction(self, direction):
        self.update_shape({})
        
    def set_axis(self, axis):
        self.update_shape({})


class OccFace(OccDependentShape, ProxyFace):
    #: A reference to the toolkit shape created by the proxy.
    shape = Typed(BRepBuilderAPI_MakeFace)
    
    def set_wires(self, wires):
        self.create_shape()
    
    def update_shape(self, change):
        d = self.declaration
        if d.wires:
            wires = d.wires
        else:
            wires = [c for c in self.children() if isinstance(c, OccShape)]
        if not wires:
            raise ValueError("No wires or children available to "
                             "create a face!")
        for i, wire in enumerate(wires):
            if hasattr(wire, 'shape'):
                args = (wire.shape.Wire(),)
            else:
                args = (wire,)
            if i == 0:
                shape = BRepBuilderAPI_MakeFace(*args)
            else:
                shape.Add(*args)
        self.shape = shape


class OccBox(OccShape, ProxyBox):
    reference = set_default('https://dev.opencascade.org/doc/refman/html/'
                            'class_b_rep_prim_a_p_i___make_box.html')

    def create_shape(self):
        d = self.declaration
        self.shape = BRepPrimAPI_MakeBox(coerce_axis(d.axis),
                                         d.dx, d.dy, d.dz)

    def set_dx(self, dx):
        self.create_shape()
    
    def set_dy(self, dy):
        self.create_shape()
    
    def set_dz(self, dz):
        self.create_shape()


class OccCone(OccShape, ProxyCone):
    reference = set_default('https://dev.opencascade.org/doc/refman/html/'
                            'class_b_rep_prim_a_p_i___make_cone.html')
    
    def create_shape(self):
        d = self.declaration
        args = [coerce_axis(d.axis), d.radius, d.radius2, d.height]
        if d.angle:
            args.append(d.angle)
        self.shape = BRepPrimAPI_MakeCone(*args)

    def set_radius(self, r):
        self.create_shape()
    
    def set_radius2(self, r):
        self.create_shape()
    
    def set_height(self, height):
        self.create_shape()
        
    def set_angle(self, a):
        self.create_shape()


class OccCylinder(OccShape, ProxyCylinder):
    reference = set_default('https://dev.opencascade.org/doc/refman/html/'
                            'class_b_rep_prim_a_p_i___make_cylinder.html')
    
    def create_shape(self):
        d = self.declaration
        args = [coerce_axis(d.axis), d.radius, d.height]
        if d.angle:
            args.append(d.angle)
        self.shape = BRepPrimAPI_MakeCylinder(*args)

    def set_radius(self, r):
        self.create_shape()
        
    def set_angle(self, angle):
        self.create_shape()
    
    def set_height(self, height):
        self.create_shape()


class OccHalfSpace(OccShape, ProxyHalfSpace):
    reference = set_default('https://dev.opencascade.org/doc/refman/html/'
                            'class_b_rep_prim_a_p_i___make_half_space.html')
    
    def create_shape(self):
        d = self.declaration
        self.shape = BRepPrimAPI_MakeHalfSpace(d.surface, gp_Pnt(*d.position))
        
    def set_surface(self, surface):
        self.create_shape()


class OccPrism(OccDependentShape, ProxyPrism):
    reference = set_default('https://dev.opencascade.org/doc/refman/html/'
                            'class_b_rep_prim_a_p_i___make_prism.html')
    
    def update_shape(self, change):
        d = self.declaration
        
        if d.shape:
            c = d.shape.proxy
        else:
            c = self.get_shape()
        
        if d.infinite:
            self.shape = BRepPrimAPI_MakePrism(
                c.shape.Shape(),
                gp_Dir(*d.direction),
                True,
                d.copy,
                d.canonize)
        else:
            self.shape = BRepPrimAPI_MakePrism(
                c.shape.Shape(),
                gp_Vec(*d.vector),
                d.copy,
                d.canonize)

    def get_shape(self):
        for child in self.children():
            if isinstance(child, OccShape):
                return child
    
    def set_shape(self, shape):
        self.update_shape({})
        
    def set_infinite(self, infinite):
        self.update_shape({})
        
    def set_copy(self, copy):
        self.update_shape({})
        
    def set_canonize(self, canonize):
        self.update_shape({})
        
    def set_direction(self, direction):
        self.update_shape({})
        
    def set_vector(self, vector):
        self.update_shape({})


class OccSphere(OccShape, ProxySphere):
    reference = set_default('https://dev.opencascade.org/doc/refman/html/'
                            'class_b_rep_prim_a_p_i___make_sphere.html')
    
    def create_shape(self):
        d = self.declaration
        args = [coerce_axis(d.axis), d.radius]
        #: Ugly...
        if d.angle:
            args.append(d.angle)
            if d.angle2:
                args.append(d.angle2)
                if d.angle3:
                    args.append(d.angle3)
        self.shape = BRepPrimAPI_MakeSphere(*args)
        
    def set_radius(self, r):
        self.create_shape()
        
    def set_angle(self, a):
        self.create_shape()
        
    def set_angle2(self, a):
        self.create_shape()
        
    def set_angle3(self, a):
        self.create_shape()


class OccTorus(OccShape, ProxyTorus):

    reference = set_default('https://dev.opencascade.org/doc/refman/html/'
                            'class_b_rep_prim_a_p_i___make_torus.html')
    
    def create_shape(self):
        d = self.declaration
        args = [coerce_axis(d.axis), d.radius, d.radius2]
        #: Ugly...
        if d.angle:
            args.append(d.angle)
            if d.angle2:
                args.append(d.angle2)
        self.shape = BRepPrimAPI_MakeTorus(*args)
        
    def set_radius(self, r):
        self.create_shape()
        
    def set_radius2(self, r):
        self.create_shape()
        
    def set_angle(self, a):
        self.create_shape()
        
    def set_angle2(self, a):
        self.create_shape()
        

class OccWedge(OccShape, ProxyWedge):

    reference = set_default('https://dev.opencascade.org/doc/refman/html/'
                            'class_b_rep_prim_a_p_i___make_wedge.html')
    
    def create_shape(self):
        d = self.declaration
        self.shape = BRepPrimAPI_MakeWedge(coerce_axis(d.axis), 
                                           d.dx, d.dy, d.dz, d.itx)

    def set_dx(self, dx):
        self.create_shape()
    
    def set_dy(self, dy):
        self.create_shape()
    
    def set_dz(self, dz):
        self.create_shape()
        
    def set_itx(self, itx):
        self.create_shape()


class OccRevol(OccDependentShape, ProxyRevol):

    #: Update the class reference
    reference = set_default('https://dev.opencascade.org/doc/refman/html/'
                            'class_b_rep_prim_a_p_i___make_wedge.html')

    def update_shape(self, change):
        d = self.declaration
        
        c = d.shape if d.shape else self.get_shape()
        
        #: Build arguments
        args = [c.shape.Shape(), gp_Ax1(gp_Pnt(*d.position), 
                                        gp_Dir(*d.direction))]
        if d.angle:
            args.append(d.angle)
        args.append(d.copy)
        
        self.shape = BRepPrimAPI_MakeRevol(*args)
    
    def get_shape(self):
        """ Get the first child shape """
        for child in self.children():
            if isinstance(child, OccShape):
                return child
    
    def set_shape(self, shape):
        self.update_shape({})
        
    def set_angle(self, angle):
        self.update_shape({})
        
    def set_copy(self, copy):
        self.update_shape({})
        
    def set_direction(self, direction):
        self.update_shape({})


class OccRawShape(OccShape, ProxyRawShape):
    #: Update the class reference
    reference = set_default('https://dev.opencascade.org/doc/refman/html/'
                            'class_topo_d_s___shape.html')

    #: The shape created
    shape = Instance(TopoDS_Shape)

    def create_shape(self):
        """ Delegate shape creation to the declaration implementation. """
        self.shape = self.declaration.create_shape(self.parent_shape())

    # -------------------------------------------------------------------------
    # ProxyRawShape API
    # -------------------------------------------------------------------------
    def get_shape(self):
        """ Retrieve the underlying toolkit shape.
        """
        return self.shape


class OccLoadShape(OccShape, ProxyLoadShape):
    #: Update the class reference
    reference = set_default('https://dev.opencascade.org/doc/refman/html/'
                            'class_topo_d_s___shape.html')

    #: The shape created
    shape = Instance(BRepBuilderAPI_Transform)

    def create_shape(self):
        """ Create the shape by loading it from the given path. """
        shape = self.load_shape()
        t = self.get_transform()
        self.shape = BRepBuilderAPI_Transform(shape, t, False)

    def get_transform(self):
        d = self.declaration
        t = gp_Trsf()
        #p = d.position
        t.SetTransformation(gp_Ax3(coerce_axis(d.axis)))
        return t

    def load_shape(self):
        d = self.declaration
        if not os.path.exists(d.path):
            raise ValueError("Can't load shape from `{}`, "
                             "the path does not exist".format(d.path))
        path, ext = os.path.splitext(d.path)
        name = ext[1:] if d.loader == 'auto' else d.loader
        loader = getattr(self, 'load_{}'.format(name.lower()))
        return loader(d.path)

    def load_brep(self, path):
        """ Load a brep model """
        shape = TopoDS_Shape()
        builder = BRep_Builder()
        breptools_Read(shape, path, builder)
        return shape

    def load_iges(self, path):
        """ Load an iges model """
        reader = IGESControl_Reader()
        status = reader.ReadFile(path)
        if status != IFSelect_RetDone:
            raise ValueError("Failed to load: {}".format(path))
        reader.PrintCheckLoad(False, IFSelect_ItemsByEntity)
        reader.PrintCheckTransfer(False, IFSelect_ItemsByEntity)
        ok = reader.TransferRoots()
        return reader.Shape(1)

    def load_step(self, path):
        """ Alias for stp """
        return self.load_stp(path)

    def load_stp(self, path):
        """ Load a stp model """
        reader = STEPControl_Reader()
        status = reader.ReadFile(path)
        if status != IFSelect_RetDone:
            raise ValueError("Failed to load: {}".format(path))
        reader.PrintCheckLoad(False, IFSelect_ItemsByEntity)
        reader.PrintCheckTransfer(False, IFSelect_ItemsByEntity)
        ok = reader.TransferRoot()
        return reader.Shape(1)

    def load_stl(self, path):
        """ Load a stl model """
        reader = StlAPI_Reader()
        shape = TopoDS_Shape()
        reader.Read(shape, path)
        return shape

    # -------------------------------------------------------------------------
    # ProxyLoadShape API
    # -------------------------------------------------------------------------
    def set_path(self, path):
        self.create_shape()

    def set_loader(self, loader):
        self.create_shape()
